Coverage Report

Created: 2026-02-05 09:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
D:\a\scloud-dns\scloud-dns\src\dns\resolver\stub\mod.rs
Line
Count
Source
1
use crate::config::Config;
2
use crate::dns::packet::DNSPacket;
3
use crate::dns::packet::question::QuestionSection;
4
use crate::dns::resolver::check_answer_diff;
5
use crate::exceptions::SCloudException;
6
use std::path::Path;
7
8
/// A simple DNS stub resolver.
9
///
10
/// The StubResolver sends DNS queries to an upstream DNS server (typically
11
/// a recursive resolver) using UDP, waits for a response, validates it,
12
/// and returns a parsed `DNSPacket`.
13
///
14
/// It supports:
15
/// - configurable timeout
16
/// - retry logic
17
/// - basic DNS response validation (ID, QR flag, section consistency)
18
#[derive(Debug, PartialEq)]
19
pub struct StubResolver {
20
    pub(crate) server: std::net::SocketAddr,
21
    pub(crate) timeout: std::time::Duration,
22
    pub(crate) retries: u8,
23
}
24
25
impl StubResolver {
26
    /// Create a new StubResolver targeting the given DNS server.
27
    ///
28
    /// Configuration values (timeout, etc.) are loaded from `config/config.json`.
29
    ///
30
    /// # Exemple :
31
    /// ```
32
    /// use std::net::SocketAddr;
33
    /// use crate::dns::resolver::StubResolver;
34
    ///
35
    /// let server: SocketAddr = "8.8.8.8:53".parse().unwrap();
36
    /// let resolver = StubResolver::new(server);
37
    ///
38
    /// assert_eq!(resolver.server, server);
39
    /// ```
40
4
    pub fn new(server: std::net::SocketAddr) -> Self {
41
4
        let config = Config::from_file(Path::new("./config/config.json")).unwrap();
42
4
        Self {
43
4
            server,
44
4
            timeout: std::time::Duration::from_secs(config.server.graceful_shutdown_timeout_secs),
45
4
            retries: 3,
46
4
        }
47
4
    }
48
49
    /// Resolve one or more DNS questions using the configured upstream server.
50
    ///
51
    /// This function:
52
    /// - builds a DNS query packet
53
    /// - sends it over UDP
54
    /// - waits for a valid DNS response
55
    /// - retries on timeout
56
    /// - validates the response ID and sections
57
    ///
58
    /// # Exemple :
59
    /// ```
60
    /// use std::net::SocketAddr;
61
    /// use crate::dns::resolver::StubResolver;
62
    /// use crate::dns::packet::question::QuestionSection;
63
    /// use crate::dns::q_type::DNSRecordType;
64
    /// use crate::dns::q_class::DNSClass;
65
    ///
66
    /// let resolver = StubResolver::new("8.8.8.8:53".parse::<SocketAddr>().unwrap());
67
    ///
68
    /// let questions = vec![QuestionSection {
69
    ///     q_name: "example.com".to_string(),
70
    ///     q_type: DNSRecordType::A,
71
    ///     q_class: DNSClass::IN,
72
    /// }];
73
    ///
74
    /// let response = resolver.resolve(questions).unwrap();
75
    ///
76
    /// assert!(response.header.qr); // Must be a response
77
    /// assert!(!response.answers.is_empty());
78
    /// ```
79
1
    pub fn resolve(&self, questions: Vec<QuestionSection>) -> Result<DNSPacket, SCloudException> {
80
1
        let packet = DNSPacket::new_query(&questions.as_slice());
81
1
        let request_id = packet.header.id;
82
83
1
        let socket = std::net::UdpSocket::bind("0.0.0.0:0")
84
1
            .map_err(|_| SCloudException::SCLOUD_STUB_RESOLVER_FAILED_TO_CREATE_SOCKET)
?0
;
85
1
        socket
86
1
            .set_read_timeout(Some(self.timeout))
87
1
            .map_err(|_| SCloudException::SCLOUD_STUB_RESOLVER_FAILED_TO_READ_SOCKET_TIMEOUT)
?0
;
88
89
1
        let bytes = packet.to_bytes()
?0
;
90
1
        socket
91
1
            .send_to(&bytes, self.server)
92
1
            .map_err(|_| SCloudException::SCLOUD_STUB_RESOLVER_FAILED_TO_SEND_TO_SOCKET)
?0
;
93
94
1
        let mut buf = [0u8; 512];
95
96
1
        let mut _last_err = None;
97
1
        for attempt in 1..=self.retries {
98
1
            println!("[STUB_RESOLVER] Attempt {}/{}", attempt, self.retries);
99
1
            match socket.recv_from(&mut buf) {
100
1
                Ok((size, _)) => {
101
1
                    let response = DNSPacket::from_bytes(&buf[..size])
?0
;
102
103
1
                    if response.header.id != request_id {
104
0
                        return Err(SCloudException::SCLOUD_STUB_RESOLVER_INVALID_DNS_ID)?;
105
1
                    }
106
107
1
                    if !response.header.qr {
108
0
                        return Err(SCloudException::SCLOUD_STUB_RESOLVER_INVALID_DNS_RESPONSE)?;
109
1
                    }
110
111
1
                    if let Err(
e0
) = check_answer_diff(
112
1
                        &questions,
113
1
                        &*response.answers,
114
1
                        &*response.authorities,
115
1
                        &*response.additionals,
116
1
                    ) {
117
0
                        return Err(e);
118
1
                    }
119
120
1
                    return Ok(response);
121
                }
122
0
                Err(e) => {
123
0
                    println!("[STUB_RESOLVER] recv_from error: {:?}", e);
124
0
                    if e.kind() == std::io::ErrorKind::WouldBlock
125
0
                        || e.kind() == std::io::ErrorKind::TimedOut
126
                    {
127
0
                        _last_err = Some(e);
128
0
                        continue;
129
                    } else {
130
0
                        return Err(
131
0
                            SCloudException::SCLOUD_STUB_RESOLVER_FAILED_TO_RECV_FROM_SOCKET,
132
0
                        );
133
                    }
134
                }
135
            }
136
        }
137
0
        Err(SCloudException::SCLOUD_STUB_RESOLVER_FAILED_TO_RECV_FROM_SOCKET)
138
1
    }
139
}